import datetime
import random
import re
import os

import handlers

from django.db import models
from django.contrib.auth.models import User, AnonymousUser
from django.contrib.sites.models import Site
from django.utils.hashcompat import sha_constructor
from django.conf import settings
from django.core.mail import EmailMessage
from django.template.loader import render_to_string

from paypal.standard.ipn.signals import payment_was_successful

from cStringIO import StringIO

from java.io import ByteArrayOutputStream
from java.lang import String
from java.nio.charset import Charset

from com.itextpdf.text import Document
from com.itextpdf.text.pdf import PdfReader, PdfStamper, PdfCopy

SIMPLE_STATUS_INACTIVE = 0
SIMPLE_STATUS_ACTIVE   = 1
SIMPLE_STATUS_CHOICES  = (
    (SIMPLE_STATUS_INACTIVE, u'Inactive'),
    (SIMPLE_STATUS_ACTIVE,   u'Active'),
)

""" Client requested for CAIPS, consent form(s) was sent by email """
#REQUEST_STATUS_SERVICE_REQUESTED = 'SR'
""" Client applied for CAIPS request and paid money """
#REQUEST_STATUS_APPLIED = 'A'
""" Agent sent package to Visa office """
#REQUEST_STATUS_SENT_TO_GOV = 'SG'
""" Agent sent CAIPS form to client """
#REQUEST_STATUS_SENT_TO_CLIENT = 'C'

#REQUEST_STATUS_CHOICES = (
#    (REQUEST_STATUS_SERVICE_REQUESTED, u'Service requested'),
#    (REQUEST_STATUS_APPLIED, u'Applied for CAIPS'),
#    (REQUEST_STATUS_SENT_TO_GOV, u'Sent to CIC'),
#    (REQUEST_STATUS_SENT_TO_CLIENT, u'Sent to Client'),
#)

""" Customer requested blank forms """
REQUEST_STATUS_UNCONFIRMED = 'UN'

""" Client submitted signed-off forms and payed fee """
REQUEST_STATUS_ORIGINATED = 'OR'

""" Agent verified everything ok """
REQUEST_STATUS_PROCESSED = 'PR'

""" Agent printed package and sent it to processing center """
REQUEST_STATUS_SUBMITTED = 'SB'

""" Results has been received; information is sent to customer """
REQUEST_STATUS_CLOSED = 'CL'

""" Agent identified problem and sent MIR to user """
REQUEST_STATUS_PENDING = 'PN'

REQUEST_STATUS_CHOICES = (
    (REQUEST_STATUS_UNCONFIRMED, u'Unconfirmed request'),
    (REQUEST_STATUS_ORIGINATED, u'Originated'),
    (REQUEST_STATUS_PROCESSED, u'In progress'),
    (REQUEST_STATUS_SUBMITTED, u'Sent to processing center'),
    (REQUEST_STATUS_PENDING, u'Pending'),
    (REQUEST_STATUS_CLOSED, u'Closed'),
)

class Country(models.Model):
    name = models.CharField(max_length=80)
    status = models.IntegerField(choices=SIMPLE_STATUS_CHOICES)
    
    class Meta:
        ordering = ("name",)
        verbose_name = u'Country'
        verbose_name_plural = u'Countries'
    
    def __unicode__(self):
        return self.name

class VisaOffice(models.Model):
    country = models.ForeignKey(Country)
    name = models.CharField(max_length=80)
    address = models.CharField(max_length=100)
    status = models.IntegerField(choices=SIMPLE_STATUS_CHOICES)
    
    class Meta:
        ordering = ("name",)
        verbose_name = u'Visa office'
        verbose_name_plural = u'Visa offices'
    
    def __unicode__(self):
        return self.name

# required for checking reference_key
SHA1_RE = re.compile('^[a-f0-9]{40}$')

class CaipsRequestManager(models.Manager):
    """
    Custom manager for the ``CaipsRequest`` model.
    
    The methods defined here provide shortcuts for key creation.    
    """
    def generate_reference_key(self):
        """
        The reference key for the ``CaipsRequest`` will be a
        SHA1 hash, generated from a combination of current timestamp
        and a random salt.
        """
        salt = sha_constructor(str(random.random())).hexdigest()[:5]
        reference_key = sha_constructor(salt + str(datetime.datetime.now())).hexdigest()
        return reference_key
    
    def get_caips_request(self, reference_key):
        """
        Validate an reference key and return the corresponding
        ``CaipsRequest`` if valid.
        
        If the key is valid and has not expired, return the ``CaipsRequest``.
        
        If the key is not valid or has expired, return ``False``.
        
        If the key is valid but the ``CaipsRequest`` is already managed,
        return ``False``.        
        """
        
        # Make sure the key we're trying conforms to the pattern of a
        # SHA1 hash; if it doesn't, no point trying to look it up in
        # the database.
        if SHA1_RE.search(reference_key):
            try:
                caips_request = self.get(reference_key=reference_key)
            except self.model.DoesNotExist:
                return False
            
            # TODO: check expiration later on
            return caips_request
        
        # else
        return False
    
class CaipsRequest(models.Model):
    user = models.ForeignKey(User, null=True)
    visa_office = models.ForeignKey(VisaOffice)
    file_no = models.CharField(max_length=15)
    email = models.EmailField(blank=True)
    date_requested = models.DateTimeField(auto_now_add=True)
    date_updated = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=10, choices=REQUEST_STATUS_CHOICES)
    is_paid = models.BooleanField(default=0)
    
    # TODO: install Django path for DecimalField 
    # (line 671 in db/fields/__init__.py: return decimal.Decimal(str(value)) )
    amount_paid = models.DecimalField(max_digits=14, decimal_places=4, default=0)

    payment_method = models.CharField(max_length=20, blank=True)
    reference_key = models.CharField(max_length=40)
    caips_form = models.FileField(upload_to='caips')
    
    objects = CaipsRequestManager()
    
    def save(self, *args, **kwargs):
        """
        Overrides existing save() method to generate reference key for new
        records only.
        """
        if self.pk is None:
            self.reference_key = CaipsRequest.objects.generate_reference_key()
        super(CaipsRequest, self).save(*args, **kwargs)
    
    def __unicode__(self):
        return self.file_no

#class ApplicantType(models.Model):
#    name = models.CharField(max_length=45)
#    status = models.IntegerField(choices=SIMPLE_STATUS_CHOICES)
#    
#    def __unicode__(self):
#        return self.name

class ConsentForm(models.Model):
    request = models.ForeignKey(CaipsRequest)
    #applicant_type = models.ForeignKey(ApplicantType)
    first_name = models.CharField(max_length=45)
    middle_name = models.CharField(max_length=45, blank=True)
    last_name = models.CharField(max_length=45)
    birthdate = models.DateField()
    is_principal = models.BooleanField(u'Principal applicant', default=False)
    consent_form = models.FileField(upload_to='consents')
    
    def __unicode__(self):
        return u'%s %s' % (self.first_name, self.last_name,)


class ServiceRequestManager(models.Manager):
    def create_initial_request(self, user, email, visa_office, file_no):
        reference_key = self.generate_reference_key()
        
        if isinstance(user, AnonymousUser):
            user = None
            
        new_sr = ServiceRequest.objects.create(user=user,
                                               email=email,
                                               visa_office=visa_office,
                                               file_no=file_no,
                                               reference_key=reference_key,
                                               status=REQUEST_STATUS_UNCONFIRMED)
        return new_sr

    def get_by_reference_key(self, reference_key):
        """
        Validate an reference key and return the corresponding
        ``CaipsRequest`` if valid.
        
        If the key is valid and has not expired, return the ``CaipsRequest``.
        
        If the key is not valid or has expired, return ``False``.
        
        If the key is valid but the ``CaipsRequest`` is already managed,
        return ``False``.        
        """
        
        # Make sure the key we're trying conforms to the pattern of a
        # SHA1 hash; if it doesn't, no point trying to look it up in
        # the database.
        if SHA1_RE.search(reference_key):
            try:
                caips_request = self.get(reference_key=reference_key)
            except self.model.DoesNotExist:
                return False
            
            # TODO: check expiration later on
            return caips_request
        
        # else
        return False

    def generate_reference_key(self):
        """
        The reference key for the ``CaipsRequest`` will be a
        SHA1 hash, generated from a combination of current timestamp
        and a random salt.
        """
        salt = sha_constructor(str(random.random())).hexdigest()[:5]
        reference_key = sha_constructor(salt + str(datetime.datetime.now())).hexdigest()
        return reference_key

class ServiceRequest(models.Model):
    """ Unique record identifier used in URLs """
    reference_key = models.CharField(max_length=40)
    
    """ It is not needed to have an account """
    user = models.ForeignKey(User, null=True)
    email = models.EmailField(blank=True)
    
    visa_office = models.ForeignKey(VisaOffice)
    file_no = models.CharField(max_length=15)
    date_requested = models.DateTimeField(auto_now_add=True)
    date_updated = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=10, choices=REQUEST_STATUS_CHOICES)
    is_paid = models.BooleanField(default=0)

    # TODO: install Django path for DecimalField 
    # (line 671 in db/fields/__init__.py: return decimal.Decimal(str(value)) )
    amount_paid = models.DecimalField(max_digits=14, decimal_places=4, default=0)
    date_paid = models.DateTimeField(null=True)
    payment_transaction_id = models.CharField(max_length=250)
    invoice_number = models.CharField(max_length=250)
    payment_status = models.CharField(max_length=20)
    payment_method = models.CharField(max_length=20)

    """ Electronic copy of CAIPS form """
    caips_form = models.FileField(upload_to='sr', blank=True)

    objects = ServiceRequestManager()

    def generate_consent_forms(self, send_email=False):
        applicants = self.applicant_set.all()
        
        # Create in-memory PDF form
        baos = ByteArrayOutputStream()
        document = Document()
        copy = PdfCopy(document, baos)
        document.open()
        
        principal_applicant = None
        
        for applicant in applicants:
            if applicant.is_principal:
                principal_applicant = applicant

            buff = applicant.generate_consent_form()
            reader = PdfReader(buff)
            page = copy.getImportedPage(reader, 1)
            copy.addPage(page)
            copy.freeReader(reader)
        
        document.close()
        
        # Convert to Python string
        pdf = StringIO()
        iso_str = String(baos.toByteArray(), Charset.forName('ISO-8859-1'))
        pdf.write(iso_str)
        content = pdf.getvalue()
        baos.close()
 
        
        if send_email:
            site = Site.objects.get_current()
            ctx_dict = {'reference_key': self.reference_key,
                        'applicant': principal_applicant,
                        'site': site}
            
            subject = render_to_string('notification/initial_request_email_subject.txt', ctx_dict)
            # Email subject *must not* contain newlines
            subject = ''.join(subject.splitlines())
            
            message = render_to_string('notification/initial_request_email_body.txt', ctx_dict)
            
            self.send_email(subject, message, content)
        
        return content

    def send_email(self, subject, message, data):
        if self.user is not None:
            to_email = [self.user.email]
        else:
            to_email = [self.email]
            
        email = EmailMessage(subject, message, settings.DEFAULT_FROM_EMAIL, to_email)
        email.attach('consent_form_imm5475e.pdf', data, 'application/pdf')
        email.send()
        
        #if self.user is not None:
        #    self.user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
        #else:
        #    if self.email is not None:
        #        #Sends an e-mail to customer
        #        from django.core.mail import send_mail
        #        send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [self.email])

    def __unicode__(self):
        return u'Service request for customer with file number %s in status %s' % (self.file_no, self.status,)

payment_was_successful.connect(handlers.confirm_payment, dispatch_uid='caips.confirm_payment.signal.handler')

class Applicant(models.Model):
    request = models.ForeignKey(ServiceRequest)
    #applicant_type = models.ForeignKey(ApplicantType)
    first_name = models.CharField(max_length=45)
    middle_name = models.CharField(max_length=45, blank=True)
    last_name = models.CharField(max_length=45)
    birthdate = models.DateField(u'Date of birth')
    is_principal = models.BooleanField(u'Principal applicant', default=False)
    consent_form = models.FileField(upload_to='consents', blank=True)
    
    def __unicode__(self):
        return u'%s %s' % (self.first_name, self.last_name,)
    
    def generate_consent_form(self):
        src = os.path.join(settings.CAIPS_TEMPLATE_DIR, 'IMM5475E.pdf')
        reader = PdfReader(src)
        baos = ByteArrayOutputStream()
        stamper = PdfStamper(reader, baos)
        
        form = stamper.getAcroFields()
        
        # applicant information
        form.setField('choose_one', 'authorize')
        form.setField('1_familyname', self.last_name)
        form.setField('1a_givname', self.first_name)
        form.setField('1b_givname', self.middle_name)
        form.setField('dob_day2', self.birthdate.strftime('%d'))
        form.setField('dob_month2', self.birthdate.strftime('%m'))
        form.setField('dob_year2', self.birthdate.strftime('%Y'))
        form.setField('3_office_name', self.request.visa_office.name)
        form.setField('3_office_location', self.request.visa_office.address)
        form.setField('3_application_type', 'Permanent residence')
        
        # firm information
        form.setField('5_familyname', settings.AGENT_INFORMATION['AGENT_LAST_NAME'])
        form.setField('5a_givname', settings.AGENT_INFORMATION['AGENT_FIRST_NAME'])
        
        form.setField('6firmname', settings.AGENT_INFORMATION['ORG_NAME'])
        form.setField('6firmname_a', settings.AGENT_INFORMATION['MAILING_ADDRESS']['ADDRESS_1'])
        form.setField('6firmname_b', settings.AGENT_INFORMATION['MAILING_ADDRESS']['ADDRESS_2'])
        
        form.setField('6city', settings.AGENT_INFORMATION['MAILING_ADDRESS']['CITY'])
        form.setField('6province/state/territory', settings.AGENT_INFORMATION['MAILING_ADDRESS']['PROVINCE'])
        form.setField('6country', settings.AGENT_INFORMATION['MAILING_ADDRESS']['COUNTRY'])
        form.setField('6postalcode/zip', settings.AGENT_INFORMATION['MAILING_ADDRESS']['POSTAL_CODE'])
        
        form.setField('tel.cc', settings.AGENT_INFORMATION['PHONE']['COUNTRY_CODE'])
        form.setField('tel.ac', settings.AGENT_INFORMATION['PHONE']['AREA_CODE'])
        form.setField('tel_number', settings.AGENT_INFORMATION['PHONE']['NUMBER'])
        
        form.setField('fax.cc', settings.AGENT_INFORMATION['FAX']['COUNTRY_CODE'])
        form.setField('fax.ac', settings.AGENT_INFORMATION['FAX']['AREA_CODE'])
        form.setField('fax_number', settings.AGENT_INFORMATION['FAX']['NUMBER'])
        
        form.setField('email', settings.AGENT_INFORMATION['EMAIL'])
        
        # declaration date
        dt = datetime.datetime.now()
        form.setField('day7a', dt.strftime('%d'))
        form.setField('month7a', dt.strftime('%m'))
        form.setField('year7a', dt.strftime('%Y'))
        
        stamper.setFormFlattening(True)
        stamper.close()
        
        buff = baos.toByteArray()
        baos.close()
        
        return buff


